package ar.edu.utn.frba.dds.tp.tests.usuarios;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

import org.joda.time.LocalDate;
import org.junit.Before;
import org.junit.Test;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.lanchita.AerolineaLanchita;
import com.oceanic.AerolineaOceanic;
import com.oceanic.AsientoDTO;

import junit.framework.Assert;
import ar.edu.utn.frba.dds.tp.code.Asientos.AsientoDeVuelo;
import ar.edu.utn.frba.dds.tp.code.Asientos.ClaseAsiento;
import ar.edu.utn.frba.dds.tp.code.Asientos.EstadoAsiento;
import ar.edu.utn.frba.dds.tp.code.adapter.LanchitaAdapter;
import ar.edu.utn.frba.dds.tp.code.adapter.OceanicAdapter;
import ar.edu.utn.frba.dds.tp.code.dateParser.ParserFecha;
import ar.edu.utn.frba.dds.tp.code.dateParser.FormatoSimple;
import ar.edu.utn.frba.dds.tp.code.filtros.PredicateClaseAsiento;
import ar.edu.utn.frba.dds.tp.code.lugares.Lugar;
import ar.edu.utn.frba.dds.tp.code.ordenamientos.CriterioOrdenVuelos;
import ar.edu.utn.frba.dds.tp.code.ordenamientos.OrdenarPorPrecioAscendente;
import ar.edu.utn.frba.dds.tp.code.usuarios.UsuarioSinCuentaPaga;
import ar.edu.utn.frba.dds.tp.code.usuarios.UsuarioStandard;
import ar.edu.utn.frba.dds.tp.code.usuarios.Usuario;
import ar.edu.utn.frba.dds.tp.code.usuarios.UsuarioVIP;
import ar.edu.utn.frba.dds.tp.code.vuelos.*;

public class UsuarioTest {
	

	AerolineaLanchita aerolineaLanchita; 
	Aerolinea lanchitaAdapter;
	Aerolinea oceanicAdapter;
	Lugar origen = new Lugar("PER");
	Lugar destino = new Lugar("USA");
	LocalDate fechaSalida = ParserFecha.Parsear("2012-10-10",new FormatoSimple("yyyy-MM-dd"));
	Predicate<AsientoDeVuelo> filtroClaseEjecutiva;

	Predicate<Vuelo> predicateVuelosConAsientosSuperOferta;
	Predicate<Vuelo> predicateVuelosConAsientosReservados;
	Predicate<Vuelo> predicateVuelosSinAsientosEjecutivo;
	Predicate<Vuelo> vuelosDeOceanic;
	CriterioOrdenVuelos precioAscendente = new OrdenarPorPrecioAscendente();
	
	@Before
	public void setUp() {
		
		BuscadorVuelos.getInstance().clearAerolineas();
		
		this.lanchitaAdapter = new LanchitaAdapter();
		this.oceanicAdapter = new OceanicAdapter();
		//instanciando otra Lanchita y modificando la instancia del adapter para que apunte a ésta por reflection.
		try{
			Constructor<AerolineaLanchita> cons = AerolineaLanchita.class.getDeclaredConstructor();
			cons.setAccessible(true);
				//devuelve nueva instancia de lanchita
			aerolineaLanchita = cons.newInstance();
			Field field = lanchitaAdapter.getClass().getDeclaredField("aerolineaLanchita");
				//agrega la nueva instancia de lanchita al adapter, para que este use la 
				//la nueva instancia y no la de posta
			field.setAccessible(true);
			field.set(lanchitaAdapter, aerolineaLanchita);
			
			Field field2;
			field2 = OceanicAdapter.class.getDeclaredField("aerolineaOceanic");
			field2.setAccessible(true);
			field2.set(this.oceanicAdapter, new AerolineaOceanicMock());

			}
			catch (Exception e) {
				e.printStackTrace();
			}
			
			ArrayList<ClaseAsiento> ejecutiva = new ArrayList<ClaseAsiento>();
			ejecutiva.add(ClaseAsiento.EJECUTIVA);
			this.filtroClaseEjecutiva = new PredicateClaseAsiento(ejecutiva);
			this.predicateVuelosConAsientosSuperOferta = new Predicate<Vuelo>() {
				public boolean apply(Vuelo vuelo) {
					return (Iterables.any(vuelo.getAsientos(), new Predicate<AsientoDeVuelo>() {
						public boolean apply(AsientoDeVuelo asiento) {
							return (asiento.soySuperOferta());
						}
					}));
				}
			};
			this.predicateVuelosConAsientosReservados = new Predicate<Vuelo>() {
				public boolean apply(Vuelo vuelo) {
					return (Iterables.any(vuelo.getAsientos(), new Predicate<AsientoDeVuelo>() {
						public boolean apply(AsientoDeVuelo asiento) {
							return (asiento.getEstado().equals(EstadoAsiento.RESERVADO));
						}
					}));
				}
			};
			this.predicateVuelosSinAsientosEjecutivo = new Predicate<Vuelo>() {
				 
				public boolean apply(Vuelo vuelo) {
					return !(Iterables.any(vuelo.getAsientos(), filtroClaseEjecutiva));
				}
			};
			this.vuelosDeOceanic = new Predicate<Vuelo>() {
				 
				public boolean apply(Vuelo vuelo) {
					return (vuelo.getAerolinea() instanceof OceanicAdapter);
				}
			};
			RegistroCompraAsientos.getInstance().clear();
		}
	
	@Test
	public void usuarioGuardaHistorialDeBusqueda(){
		Usuario user = new UsuarioSinCuentaPaga(new BigDecimal (20));
		user.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null);
		Assert.assertFalse(user.getBusquedasRealizadas().isEmpty());
	}
	
	@Test
	public void usuarioRecienCreadoTieneHistorialVacio(){
		Usuario user = new UsuarioStandard();
		Assert.assertTrue(user.getBusquedasRealizadas().isEmpty());
	}
	
	@Test
	public void usuarioSinCuentaPagaNoEncuentraAsientosReservados() {
		Usuario user = new UsuarioSinCuentaPaga(new BigDecimal (20));
		List<Vuelo> vuelos = user.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null);
		Assert.assertFalse(Iterables.any(vuelos, this.predicateVuelosConAsientosReservados));
	}
	
	@Test
	public void usuarioVIPEncuentraAsientosReservados() {
		Usuario unUser = new UsuarioVIP();
		List<Vuelo> vuelos = unUser.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null);
		Assert.assertTrue(Iterables.any(vuelos, this.predicateVuelosConAsientosReservados));
	}
	
	@Test
	public void usuarioStandarEncuentraAsientosReservados() {
		Usuario usrStandar = new UsuarioStandard();
		List<Vuelo> vuelos = usrStandar.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null);
		Assert.assertTrue(Iterables.any(vuelos, this.predicateVuelosConAsientosReservados));
	}
	
	@Test
	public void usuarioStandarNoTieneAsientosSuperOferta() {
		Usuario pepe = new UsuarioStandard();
		List<Vuelo> vuelos = pepe.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null);
		Assert.assertFalse(Iterables.any(vuelos, this.predicateVuelosConAsientosSuperOferta));
	}
	
	@Test
	public void usuarioSinCuentaPagaNoTieneAsientosSuperOferta() {
		Usuario carlos = new UsuarioStandard();
		List<Vuelo> vuelos = carlos.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null);
		Assert.assertFalse(Iterables.any(vuelos, this.predicateVuelosConAsientosSuperOferta));
	}
	
	@Test
	public void usuarioVipTieneAsientosSuperOferta() {
		Usuario unUser = new UsuarioVIP();
		List<Vuelo> vuelos = unUser.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null);
		Assert.assertTrue(Iterables.any(vuelos, this.predicateVuelosConAsientosSuperOferta));
	}

	@Test
	public void usuarioBuscaVuelosFiltradosYOrdenados() {
		Usuario noPaga = new UsuarioSinCuentaPaga(new BigDecimal (20));
		ArrayList<Predicate<AsientoDeVuelo>> filtros = new ArrayList<Predicate<AsientoDeVuelo>>();
		filtros.add(filtroClaseEjecutiva);
		List<Vuelo> vuelos = noPaga.buscarVuelos(origen, destino, fechaSalida, filtros, this.precioAscendente);
		//Se fija que no haya asientos de clase ejecutiva y que el precio del primer asiento coincida con
		//700, que es el menor precio de los asientos no ejecutivos seteados al mock de Oceanic
		Assert.assertTrue(Iterables.any(vuelos, this.predicateVuelosSinAsientosEjecutivo)
				&& vuelos.get(vuelos.size()-1).getAsientos().get(0).getPrecio().equals(new BigDecimal(700)));
	}
	
	@Test
	public void usuarioCompraAsientoDeOceanic() {
		Usuario unUsuario = new UsuarioVIP();
		List<Vuelo> vuelos = unUsuario.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null);
		Vuelo vueloComprado = Iterables.find(vuelos, this.vuelosDeOceanic);
		AsientoDeVuelo asiento = vueloComprado.getAsientos().get(0);
		unUsuario.comprarAsiento(asiento);
		//Se fija que la compra haya quedado registrada en el registro de compras
		Assert.assertEquals(new Integer(1), RegistroCompraAsientos.getInstance().getValue(oceanicAdapter, vueloComprado));
	}

	@Test (expected = NoSuchElementException.class)
	public void usuarioCompraAsientoDeLanchita() {
		Usuario user = new UsuarioStandard();
		ArrayList<Predicate<AsientoDeVuelo>> filtros = new ArrayList<Predicate<AsientoDeVuelo>>();
		List<Vuelo> vuelos = user.buscarVuelos(new Lugar("BsAs"), new Lugar("LA"), new LocalDate(2012,3,21), filtros, null);
		Vuelo vuelo = vuelos.get(0);
		List<AsientoDeVuelo> asientos = vuelo.getAsientos();
		final AsientoDeVuelo asiento = asientos.get(0);
		user.comprarAsiento(asiento);
		
		Predicate<? super Vuelo> predicate = new Predicate<Vuelo>() {
			 
			public boolean apply(Vuelo vuelo) {
				return vuelo.getAsientos().contains(asiento);
			}
		};
		Iterables.find(user.buscarVuelos(new Lugar("BsAs"), new Lugar("LA"), new LocalDate(2012,3,21), filtros, null), predicate);
	}
	
	@Test
	public void UsuariosReservanUnAsiento() {
		UsuarioStandard usuario1 = new UsuarioStandard();
		AsientoDeVuelo asientoAReservar = usuario1.buscarVuelos(origen, destino, fechaSalida, new ArrayList<Predicate<AsientoDeVuelo>>(), null).get(0).getAsientos().get(0);
		usuario1.reservarAsiento(asientoAReservar);
		UsuarioStandard usuario2 = new UsuarioStandard();
		usuario2.reservarAsiento(asientoAReservar);
		//Dos reservas al mismo asiento indica que este va a tener sobre-reservas
		Assert.assertFalse(asientoAReservar.getSobreReservas().isEmpty());
	}
	
	
	final class AerolineaOceanicMock extends AerolineaOceanic {
		
		public List<AsientoDTO> asientosDisponiblesParaOrigenYDestino(String origen, String destino, String fechaDeSalida) {
			List<AsientoDTO> asientos = new ArrayList<AsientoDTO>();
			asientos.add(new AsientoDTO("OC100",10,"10/10/2012","11/10/2012","10:35","05:35",new BigDecimal("600"),"Ejecutiva","Pasillo",false,"PER","USA"));
			asientos.add(new AsientoDTO("OC101",11,"10/10/2012","11/10/2012","10:35","05:35",new BigDecimal("700"),"Turista","Pasillo",false,"PER","USA"));
			asientos.add(new AsientoDTO("OC101",8,"10/10/2012","11/10/2012","10:35","05:35",new BigDecimal("800"),"Turista","Pasillo",false,"PER","USA"));
			asientos.add(new AsientoDTO("OC103",9,"10/10/2012","11/10/2012","10:35","05:35",new BigDecimal("30000"),"Ejecutiva","Pasillo",true,"PER","USA"));
			return asientos;
		}
		
		public Boolean comprarSiHayDisponibilidad(String dni, String codigoVuelo,Integer numeroDeAsiento) {
			return true;
		}
		
	}
	
	@org.junit.After
	public void After(){
		BuscadorVuelos.getInstance().clearAerolineas();
		RegistroCompraAsientos.getInstance().clear();
	}
		
}
